//例如 char short int long 等这都属于C语言内置的数据类型
//一些复杂对象的并不能用上面的这些类型来描述  也就有了复杂类型也叫    自定义类型：结构体、枚举、联合体

//1.结构体类型的声明
//基本的见前面的笔记








//2.匿名结构体类型              匿名结构体只能在声明的时候创建
//struct
//{
//    int a;
//    int b;
//}x,a[20],*p;//匿名结构体类型的 变量只能刚开始进行创建，也就比如这个结构体变量x只有这一种创建的方式
//当然也也可以通过typedef将这个结构体改名字，然后用重定义的名字进行定义结构体


//struct
//{
//    int a;
//    int b;
//}*px;
//
////px=&x;结构体类型的指针指向的结构体变量的必须是和指针类型的里面的成员变量全部相同      但是这样写仍然是不合法的
////      但是匿名结构体类型的指针虽然和上面的变量成员变量是相同的。但是这两个的类型仍然是不同的，可以理解为匿名掉的标签其实是不一样的








//3.结构体的自引用    也就是包含自己同类型的指针     链表的表示方法
//#include<stdio.h>
//（1）
//struct Node//这种写法就是链表的表示方法  里面有变量和指向下一个变量的地址
//{
//    int data;//存放数据的地方叫做数据域
////    struct Node n;//这种写法是错误的，结构体自己不能包含自己类型的结构体变量            这个不能定义是因为结构体到这一步还没有完全定义出来,如果这样去定义就会导致结构体无限递归下去
//    struct Node* next;//存放指针的地方叫做指针域                 这个能定义是因为struct Node这个类型已经定义出来，指针的话能够使用
//};

//typedef struct Node
//{
//    int a;
//    struct Node* next;
//}Node;//这种写法是struct Node这个类型变为 Node这个类型              ----也就是类型重定义


//（2）
//typedef struct
//{
//    int a;
//    Node* next;//因为现在这个匿名结构体类型重命名还没有完成，所以Node在这里用时错误的。   法
//}Node;        -----也就是说在没有重定义成功之前，是不能在字段成员中进行使用的


//（3）
//typedef struct
//{
//    int a;
//    int b;
//    char name[20];
//}test1;
//test1 l;
//
//int main(void)
//{
////l={1,1"sjdka"}//这样是错误的。创建后的结构体变量不能这样赋值，每个成员变量都得写出来才能进行赋值             -----结构体只有在刚定义的时候才能初始化
//test1 l1={1,2,"zhansan"};
//printf("%d",l1.a);
//}






//4.结构体变量的定义和初始化
//前面的笔记已经很详细了





//5.结构体内存对齐
//也就是涉及到结构体内存的大小如何计算的

//第一个成员在与结构体变量偏移量为0的地址处
//（这句话的意思是，第一个成员变量存放的地址是和结构体变量名字相同的   类似于数组名的地址就是第一个元素的地址     偏移量就是相差了多少个字节）一个单元格就是一个字节
//结构体变量的地址&s1，在数值上和第一个成员变量的地址是相同的。也就是说第一个成员变量的偏移量为0

//其他成员变量要对齐到某个数字（对齐数）的整数倍的地址处。       int类型的变量如果不是第一个变量就得从第5个（格子）字节 第9个字节 第13个字节算上本身存储4个格子
//（这句话的意思是，除了第一个成员变量，其余的成员变量都要放在对齐数的整数倍的地址处）
//用偏移量来记忆会更好

//对齐数=编译器默认的一个对齐数 与 该成员大小的较小值    对齐数要么是该成员变量的类型字节数（char 1  int 4）要么是默认数，也就看谁比较小
//（这句话的意思是，对齐数是编译器默认的数字和该成员其中字节最小的那个数字）
//vs默认的对齐数是8  gcc并没有默认对齐数                对齐数会导致这些变量在内存上分布的间隔大小有差异




//结构体总大小为最大对齐数（每个成员变量都有一个对齐数）的整数倍
//如果嵌套了结构体的情况，嵌套的结构体对齐到自己的最大对齐数的整数倍处，结构体的整体大小就是所有最大对齐数（含嵌套结构体的对齐数）的整数倍
//（先计算出被嵌套结构体的总大小，然后再根据被嵌套结构体里面变量的最大对齐数进行对齐，最后再根据结构体和被嵌套结构体中的的最大对齐数的整数倍全部进行对齐）



//总结，每个成员变量在计算所占内存空间的时候都要和默认对齐数对比，如果本身类型所占字节数小于默认对齐数，就按照本身的字节数来计算
//变量所处的内存位置，在偏移量为对齐数的整数倍处。
//被嵌套的结构体先计算出嵌套结构体的大小，然后根据被嵌套结构体里面成员的的最大对齐数来进行对齐。   -----也就是将这个被嵌套结构体看作一个整体的变量
//没有嵌套结构体的结构体，在最后计算内存大小的时候，就是成员变量中最大对齐数的整数倍

//另外注意每个变量都是从第一个字节处开始计算本对齐数的倍数的
//#include<stdio.h>


//struct S1      //12
//{
//    char c1;
//    int a;
//    char c2;
//};
//
//struct S2   //8
//{
//    char c1;
//    char c2;//结构体变量换S1和S2的标签不一样  所以里面的成员变量的名字是可以相同的
//    int a;
//};
//int main(void)
//{
//    struct S1 s1={0};//不想对变量进行初始的时就可以这样
//    struct S2 s2={0};
//    printf("%d\n",sizeof(s1));//12      1+3+4+1+3=12
//    printf("%d\n",sizeof(s2));//8
//}
//s1变量内存大小输出12的原因     也就是最后一个格子占完得是对齐数的倍数
//（1）第一个变量是一个字节，占用一个格子   对齐数是1
// (2）第二个变量是一个字节，占用一个格子   对齐数是1，
//（3）第二个变量是4个字节，对齐数是4（每次占4个格子，如果第1-4的格子已经有内容了。那就看5-8的格子，再一次9-12）所以对齐数是也就是从第5个格子开始算再占4个格子，也就是占到8个字节处
//（4）最后就是看最大对齐数的倍数。取一个刚好能包含所有格子的格子数就是变量的大小的

//像这样   ———————— c1    这些都代表着地址，实际上这个单元格是一个字节          -----第一个地址的偏移量是0
//        ———————— c2
//        ————————
//        ————————
//        ———————— a
//        ————————
//        ————————
//        ————————
//        ————————
//        ————————
//        ————————
//        ———————— 变量总大小的地址处，
//        ————————

//ps：也可以这么理解，c1地址的偏移量是0，c2的偏移量是1，也就是对齐数的的整数倍也就是刚好从偏移量的那个数字开始。比如double 8，要么从头开始取，要么从偏移量为8 16 24.。。的地方开始取

//struct S3
//{
//    double d;
//    char c;
//    int i;
//};
//struct s4
//{
//    char c1;
//    struct S3 s3;
//    double d;
//};
//s4的c1占用了1个字节，s3的总大小计算出来了是16，而被嵌套结构体里面的最大对齐数是8，所以这个被嵌套对齐数要从偏移量为8的倍数处开始，然后d的对齐数是8.
// 然后整个数组的最大对齐数刚好是8，8的倍数里面有32.所以总大小32



//内存对齐的意义（中间内存对齐时出现浪费空间的作用）             其实就是为了拿空间换时间
//（1）平台原因（移植原因）：不是所有的硬件平台都能访问任意地址上的任意数据的；某些硬件平台只能在某些地址处取某些特定类型的数据，否则抛出硬件异常
//（也就是说有些硬件平台访问特定的数据类型的时候只访问4的倍数啊，8的倍数啊，这都是和变量的类型的最大对齐数有关的）

//（2）性能原因：数据结构（尤其是栈）应该尽可能的在自然界边上对齐。原因在于，为了访问未对齐的内存，处理器需要做两次内存访问；而对齐的内存访问仅需要一次访问
//（32位机器有32根地址线 也就是有32根数据线，  一次能传输4个字节  4*8=32。比如下面的这个结构体）
//struct s
//{
//    char c;
//    int a;
//};如果不进行内存对齐的话，这就存了连续的五个字节。查找内存时先查4个字节，再查4个字节，两个字节包含的内容往回拼接出一个int
//而进行内存对齐的时候，直接访问偏移量是4的倍数的地址。一次就能搞定



//总结：想让既节省空间又节省时间，尽量让占用空间小的变量集中在一起

//补充：几字节对齐就是默认对齐数为多少







//6.修改默认对齐数  #pragma pack()

//#include<stdio.h>
//#pragma pack(4)//这个语句就是让默认对齐数改为4,并没有分号；    想要改变每种类型的对齐数需要挨个提前设置
//struct S
//{
//    char c1;
//    double d;
//};
////#pragma pack()//取消设置的默认对齐数，还原为默认
//
//#pragma pack(1)
//struct T
//{
//    char c1;
//    double d;
//};
//#pragma pack()
//
//int main(void)
//{
//    struct S s;
//    struct T t;
//    printf("%d\n",sizeof(s));
//
//    printf("%d",sizeof(t));
//}










//7.offsetof();计算偏移量的宏        size_t offsetof(structName,memberName)//两个参数，一个参数放结构体类型的名字，一个参数放成员变量的名字
//只有宏定义的函数，参数才可以传类型          在学过宏后记得实现一下offsetof()这个宏
//记住这个使用的格式   offsetof(结构体类型，成员变量名字）    这个宏的返回值是偏移量
//struct s
//{
//    char c;
//    int i;
//    double d;
//    double x;
//};
//#include<stdio.h>
//#include<stddef.h>
//int main(void)
//{
//    printf("%d\n",offsetof(struct s,c));//专门计算成员变量的偏移量
//    printf("%d\n",offsetof(struct s,i));
//    printf("%d\n",offsetof(struct s,d));
//    printf("%d\n",offsetof(struct s,x));
//}








//8.结构体传参
//结构体传参，要传结构体的地址
//struct S
//{
//    int a;
//    char c;
//    double d;
//};



//结构体指针可以通过  指针名->字段成员名     的方式来访问成员变量
//void init(struct S* p)
//{
//    p->a=4;//当然也可以这样 *p.a=4   *p.c='h'，结构体类型的指针可以用  指针->   直接访问成员变量的内容
//    p->c='h';
//    p->d=3.14;
//}
// 
// 
// 
//#include<stdio.h>
//int main(void)
//{
//    struct S s={0};
//    init(&s);
//}
//传值的和传址的比较
//函数传参的时候参数是需要压栈，会有时间和空间上的系统开销
//如果传递一个结构体对象的时候，结构体过大，参数压栈的系统开销比较大，所以会导致性能的下降
//也就是说传参的时候尽量选择传址的形式

//结构体两种访问成员变量的方式
//结构体变量名字.成员变量名字
//结构体指针->成员变量名字








//9.位段
//位段的声明和结构体式类似的，但是有两个不同：
//位段的成员必须是 unsigned int 或者signed int （int默认的就是这个类型）       位段的成员名后面有一个冒号和数字
//现在的编译器也可能会出现char类型   char:4;这种情况   这样是每次只开辟一个字节。满8位再去开辟下一个字节      int类型的会一次开辟4个字节

//位段的空间上是按照需要以4个字节（int)或者1个字节(char)的方式来开辟的
//位段涉及很多不确定因素，位段是不跨平台的，注重可移植的程序应该避免使用位段

//#include<stdio.h>
//struct A
//{
//    int a:2;//后面的数字就是比特位的意思   也就是前面的变量只占用两个比特位    8位一个字节        先开批出来的空间在低地址处。因为是在栈上面存放，栈是后进先出的
//    int b:5;
//    int c:10;
//    int d:20;//一个int类型的数据是4个字节。  也就是说里面的变量 最大也就分配32个比特位，变量的位数分配最多就32
//    char g:3;//上面是int的情况下尽量别在下面创建char类型的
//};
////A就是一个位段类型      考虑一个问题，位段的大小怎么计算
//
//int main(void)
//{
//    struct A a;
//    printf("%d",sizeof(a));//2+5
//    //先找出来4个字节（32位）按照上面分配的位数进行分配空间（按照位数分配）。
//    // 如果前面的变量分配的位数相加小于32，再新添加一个变量就大于32.那么再单独为这个新的变量再去开辟4个字节           -----看这个变量是什么类型的
//    return 0;
//}


//struct S
//{
//    char a:3;
//    char b:4;
//    char c:5;
//    char d:4;
//};
//#include<stdio.h>
//int main(void)
//{
//    struct S s={0};
//    s.a=10;//赋的值超过了分配的位数所能表达的上限  比如10的二级制是1010   但是分配了3位那就只能存 010  也就是会截断
//    //首先开辟了1个字节的空间 00000000     变为00000010
//
//    s.b=20;//20的十进制是10100 存4位  开辟的第一个字节变成00100010
//
//    s.c=3;//而3的二级制是11  但是分配了5位   所以前面不够的补零  00011       开辟第二个字节进行存储  00000011
//
//    s.d=4;//再次开辟一个空间进行存储  00000100
//}
////所以上面三个变量赋值后在内存中以16进制的形式进行显示是  22 03 04 00    这也就是上面的变量存储时不按照规定进行存储
//存的数字和位段长度不匹配的时候会发生截断








//10.位段的不跨平台性
//int位段有符号数还是无符号数是不确定的
//位段中最大位的数目不能确定(16位的机器是16，32位机器是32，64位是64）
//位段中的成员在内存中从左向右分配，还是从右向左分配尚未定义
//当一个结构版含两个位段，第二个位段成员比较大，无法容纳第一个位段成员剩余的位时，是舍弃剩余的位还是利用尚未定义

//跟结构相比，位段可以达到同样的效果，但是可以更好的节省空间，但是存在跨平台的问题。


//位段的一些应用：            -----网络应用层的一些协议
//上网的时候》》》进行数据传输》》》发送了一个信息 呵呵。但是也包含了很多的其他信息（发送给谁？从哪来？等）例如网络应用层的一些协议
//发送的信息被封装为  4位版本号、4位收长度、8位服务类型、16位总长度3位标志......等信息   也就是可以更好的节省时间








//补充：
//结构体变量的类型重定义和使用方式
//typedef struct//这里也可以是typedef struct SNode
//{
//    int data;
//    int position;
//}SLinkList[10];//将这个结构体类型重定义为能创建存放10个元素的数组

//数组的定义方式是 ： SLinkList arr;这就是创建了一个能存放10个元素的数组了。  如果SLINKLIST arr[10]这样就是定义了一个二维数组